package org.aceor.mddal.client.jdbc;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import org.aceor.mddal.atom.AtomDataSourceWrapper;
import org.aceor.mddal.atom.factory.DsFactory;
import org.aceor.mddal.common.constants.Constants;
import org.aceor.mddal.common.utils.RuntimeConfigHolder;
import org.aceor.mddal.config.datasource.AtomNode;
import org.aceor.mddal.config.datasource.DsConfig;
import org.aceor.mddal.config.datasource.GroupNode;
import org.aceor.mddal.config.datasource.factory.DsConfigFactory;
import org.aceor.mddal.config.sharding.LogicTable;
import org.aceor.mddal.group.DataSourceWrapper;
import org.aceor.mddal.group.GroupDataSource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.util.List;
import java.util.Map;

/**
 * Created by lxue on 16/4/4.
 */
public class DataSourceConfig {

    private static final Logger log = LoggerFactory.getLogger(DataSourceConfig.class);

    private LogicTable logicTable;
    private String dsConfigPath;

    protected volatile boolean inited = false;
    //TODO 思考这里需要使用ThreadLocal吗?会有污染问题吗
    //使用RuntimeConfigHolder,为了当zk配置变化时可以动态修改
    protected RuntimeConfigHolder<Map<String/** groupKey **/, DataSource/** groupDs **/>> dsMapHolder = new RuntimeConfigHolder<>();

    public void init() {
        if (inited) {
            throw new RuntimeException("DataSourceConfig$init ==> 已经初始化过了");
        }
        if (Strings.isNullOrEmpty(dsConfigPath)) {
            throw new RuntimeException("DataSourceConfig$init ==> dsConfigPath is NullOrEmpty");
        }
        RuntimeConfigHolder<DsConfig> dsConfigHolder = null;
        try {
            if (Constants.IS_FETCH_DS_CONFIG_FROM_ZK) {
                //从zk获取
                dsConfigHolder = DsConfigFactory.buildDsConfig(dsConfigPath, new DsConfigFactory.DsConfigChangeListener() {
                    @Override
                    public void onChange(DsConfig dsConfig) {
                        dsMapHolder.set(buildDsMap(dsConfig));
                    }
                });
            } else {
                //从本地配置文件获取
                dsConfigHolder = DsConfigFactory.buildDsConfig(dsConfigPath, null);
            }
        } catch (Exception e){
            log.error("DataSourceConfig$init failure, dsConfigPath :{}, e :{}", dsConfigPath, ExceptionUtils.getStackTrace(e));
            throw new RuntimeException("DataSourceConfig$init failure", e);
        }
        if (dsConfigHolder.get() == null) {
            //理论上不会走到这里.因为上面的构建没有抛出异常的话,说明构建成功了
            throw new RuntimeException("dsConfigHolder中的dsConfig为空");
        }
        //构建AtomDs和GroupDs
        if (dsConfigHolder.get().isGroupEmpty()) {
            throw new RuntimeException("DataSourceConfig$init ==> group数据源为空");
        }
        dsMapHolder.set(buildDsMap(dsConfigHolder.get()));
        inited = true;
    }

    private Map<String, DataSource> buildDsMap(DsConfig dsConfig) {
        Map<String, DataSource> dsMap = Maps.newHashMap();
        for (Map.Entry<String, GroupNode> entry : dsConfig.getGroupMap().entrySet()) {
            dsMap.put(entry.getKey(), buildGroupDs(dsConfig, entry.getValue()));
        }
        return dsMap;
    }

    private DataSource buildGroupDs(DsConfig dsConfig, GroupNode groupNode) {
        GroupDataSource groupDatasource = new GroupDataSource(groupNode.getId());
        List<AtomNode> atomNodes = groupNode.getAtoms();
        if (CollectionUtils.isEmpty(atomNodes)) {
            throw new RuntimeException("DataSourceConfig$buildGroupDs ==> GroupDs["+groupNode.getId() +"] has none AtomDs");
        }
        for (int i=0; i<atomNodes.size(); i++) {
            AtomNode atomNode = atomNodes.get(i);
            groupDatasource.getDataSourceWrapperMap().put(atomNode.getRef(), buildDatasourceWrapper(dsConfig, atomNode, i));
        }
        groupDatasource.init();
        return groupDatasource;
    }

    private DataSourceWrapper buildDatasourceWrapper(DsConfig dsConfig, AtomNode atomNode, int index) {
        return new DataSourceWrapper(atomNode.getRef(), atomNode.getWeight(), buildAtomDatasourceWrapper(dsConfig, atomNode), index);
    }

    private AtomDataSourceWrapper buildAtomDatasourceWrapper(DsConfig dsConfig, AtomNode atomNode) {
        if (!dsConfig.containsAtom(atomNode.getRef())) {
            throw new RuntimeException("ref :"+atomNode.getRef()+" cannot find corresponding atom");
        }
        return new AtomDataSourceWrapper(DsFactory.buildDbcpDatasource(dsConfig.getAtom(atomNode.getRef())));
    }

    public String getDsConfigPath() {
        return dsConfigPath;
    }

    public void setDsConfigPath(String dsConfigPath) {
        this.dsConfigPath = dsConfigPath;
    }

    public LogicTable getLogicTable() {
        return logicTable;
    }

    public void setLogicTable(LogicTable logicTable) {
        this.logicTable = logicTable;
    }
}
